/*
 * Copyright 2015 Patrick Ahlbrecht
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.onyxbits.weave;

import java.util.HashMap;

/**
 * A registry for scoped singletons. The registry can be pre populated with
 * objects or generate them on the fly when requested.
 * 
 * @author patrick
 * 
 */
public class Globals {

	private HashMap<Class<?>, Object> registry = new HashMap<Class<?>, Object>();
	private GlobalsFactory factory;

	/**
	 * Convenience constructor for registering objects on instantiation
	 * 
	 * @param objs
	 *          objects to register
	 */
	public Globals(Object... objs) {
		if (objs != null) {
			for (Object o : objs) {
				put(o);
			}
		}
	}

	/**
	 * Put an object into the registry. Objects are mapped by class, so there can
	 * always only be one instance of any given class in here. Registering another
	 * object of the same class will replace the previous instance.
	 * 
	 * @param objects
	 *          the object(s) to register (may not be null).
	 */
	public void put(Object... objects) {
		for (Object object : objects) {
			registry.put(object.getClass(), object);
		}
	}

	/**
	 * Lookup an object by its class. Note: Looking up an object by interface will
	 * obviously not work since an interface can be implemented by multiple
	 * classes. If only the interface of an object is known (e.g. because it was
	 * generated by a factory), then that object must be wrapped in a container.
	 * 
	 * @param clazz
	 *          the class
	 * @return the registered object or null.
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(Class<T> clazz) {
		T ret = (T) registry.get(clazz);
		if (ret == null && factory != null) {
			ret = (T) factory.onCreate(this, clazz);
			if (ret != null) {
				put(ret);
			}
		}
		return ret;
	}

	/**
	 * Remove an object from the registry.
	 * 
	 * @param clazz
	 *          the class of the object. No-op if no object is found.
	 */
	public <T> void remove(Class<?> clazz) {
		registry.remove(clazz);
	}

	/**
	 * Create a new instance with a shallow copy of the registry.
	 * 
	 * @return a copy with a registry that can be manipulated independently.
	 */
	@SuppressWarnings("unchecked")
	public Globals copy() {
		Globals ret = new Globals();
		ret.registry = (HashMap<Class<?>, Object>) registry.clone();
		return ret;
	}

	/**
	 * Set a factory for lazily creating objects when they are requested. This
	 * method can only be called once. Subsequent calls will do nothing.
	 * <p>
	 * A factory is optional, but highly recommended for larger projects. It may
	 * reduce startup time (objects are only created when requested) and vastly
	 * simplifies dependency tracking.
	 * 
	 * @param factory
	 *          the factory responsible for creating and registering objects when
	 *          they are requested.
	 */
	public void setFactory(GlobalsFactory factory) {
		if (this.factory == null) {
			this.factory = factory;
		}
	}
}
